//
//  PSHand.m
//  PrefCore
//
//  Created by Sergey on 2/10/14.
//  Copyright (c) 2014 Play-on-smart. All rights reserved.
//

#import "PSHand.h"
#import "PSGame.h"

extern inline PSCardRankSet PSHandGetSuit(PSHandHash hand, PSCardSuit suit);

extern inline PSHandHash PSHandAddSuitFromSet(PSHandHash hand, PSCardRankSet cards, PSCardSuit suit);

extern inline PSHandHash PSHandKeepSuit(PSHandHash hand, PSCardSuit suit);

static inline PSHandHash PSHandMerge(const PSCardHash *cards, size_t count) {
    PSHandHash res = 0;
    for (size_t i = 0 ; i != count ; res |= cards[i++])
        ;
    return res;
}

PSHandHash PSHandAddCards(PSHandHash hand, const PSCardHash* cards, size_t count) {
    return hand | PSHandMerge(cards, count);
}

extern inline PSHandHash PSHandAddHand(PSHandHash one, PSHandHash two);

extern inline PSHandHash PSHandAddSuit(PSHandHash hand, PSCardSuit suit, PSHandHash otherHand);

extern inline PSHandHash PSHandWithOneCard(const PSCard *card);

extern inline PSHandHash PSHandAddCard(PSHandHash hand, const PSCard *card);

extern inline PSHandHash PSHandRemaining(PSHandHash cards[3]);

extern inline PSHandHash PSHandRemoveHand(PSHandHash hand, PSHandHash otherHand);

PSHandHash PSHandRemoveCards(PSHandHash hand, const PSCardHash *cards, size_t count) {
    return hand & ~PSHandMerge(cards, count);
}

extern inline PSHandHash PSHandRemoveSuit(PSHandHash hand, PSCardSuit suit);

extern inline BOOL PSHandContainsCard(PSHandHash hand, PSCardHash card);

extern inline BOOL PSHandContainsAllCards(PSHandHash hand, PSHandHash otherHand);

extern inline PSHandHash PSHandAdjust(PSHandHash hand, const PSCardHash *talon, PSCardHash *drop);

PSHandHash PSHandAddTrick(PSHandHash hand, PSTrickHash trick) {
    PSCard *talon = PSTrickTalonCard(trick);
    PSCard *c1 = PSTrickCard(trick, 0);
    PSCard *c2 = PSTrickCard(trick, 1);
    PSCard *c3 = PSTrickCard(trick, 2);
    // talon card is not always there
    if (talon) {
        hand = PSHandAddCard(hand, talon);
    }
    hand = PSHandAddCard(hand, c1);
    hand = PSHandAddCard(hand, c2);
    hand = PSHandAddCard(hand, c3);
    return hand;
}

PSCardHash PSHandGetFirstCard(PSHandHash hand) {
    for (PSHandHash bit = (1U << 31) ; bit ; bit >>= 1) {
        if ((hand & bit) != 0) {
            return bit;
        }
    }
    return 0;
}

PSCardHash PSHandGetLastCard(PSHandHash hand) {
    for (PSHandHash bit = 1U ; bit ; bit <<= 1) {
        if ((hand & bit) != 0) {
            return bit;
        }
    }
    return 0;
}

PSCardHash PSHandGetNextCard(PSHandHash hand, PSCardHash current) {
    while (current >>= 1) {
        if ((hand & current) != 0) {
            return current;
        }
    }
    return 0;
}

PSCardHash PSHandGetPreviousCard(PSHandHash hand, PSCardHash current) {
    while (current <<= 1) {
        if ((hand & current) != 0) {
            return current;
        }
    }
    return 0;
}

extern inline PSCardHash PSHandGetSize(PSHandHash hand);

uint8_t PSHandSuitCount(PSHandHash hand) {
    uint8_t res = 0;
    for (int i = 0 ; i != PS_CARD_SUIT_RISING_LEN ; i++) {
        PSCardSuit suit = PSCardSuitRising[i];
        if (PSHandKeepSuit(hand, suit)) {
            res++;
        }
    }
    return res;
}

PSHandHash PSHandMoveCandidates(PSHandHash hand, PSTrickHash trick, PSCardSuit trump) {
    // Try to get the first played card, initially from the talon part of trick (set in passing mode)
    PSCardSuit suit = PSTrickCardSuit(trick);
    if (suit != kSuitNone) {
        // get cards set of the trick's suit
        int set = PSHandKeepSuit(hand, suit);
        // we have the starting card here, try to return our set of cards in this suit or
        // if the trump is defined try to return trump suit cards
        if (set || (trump != kSuitNone && (set = PSHandKeepSuit(hand, trump)))) {
            return set;
        }
    }
    // Player is a starter or does not have cards of first card suit or of trump suit
    return hand;
}

PSHandHash PSHandMoveCandidatesInGame(PSGame *game, PSHandHash hand) {
    return PSHandMoveCandidates(hand, game.trick.current, game.trump);
}

static inline void PSHandSwapCards(NSMutableArray *a, PSCardIndex i, PSCardIndex j) {
    PSCard *tmp = a[i];
    [a setObject:a[j] atIndexedSubscript:i];
    [a setObject:tmp atIndexedSubscript:j];
}

NSArray* PSHandGetCards(PSHandHash hand, NSArray *suits, PSGameRankOrder rankOrder, BOOL sort) {
    NSMutableArray *res = [NSMutableArray arrayWithCapacity:PS_HAND_SIZE];
    PSCardRank *ranks = (rankOrder == kRankOrderAscending) ? PSCardRankRising : PSCardRankFalling;
    for (NSNumber *sn in suits) {
        PSCardSuit s = (PSCardSuit)[sn unsignedIntValue];
        for (int j = 0 ; j != PS_CARD_RANK_COUNT ; j++) {
            PSCardRank r = ranks[j];
            PSCard *card = [PSCard cardWithSuit:s andRank:r];
            if (PSHandContainsCard(hand, card.hash32)) {
                [res addObject:card];
            }
        }
    }
    if (!sort) {
        // Fisher–Yates 7 times
        for (int p = 0; p < 7; p++) {
            for (PSCardIndex i = res.count - 1; i > 0; i--) {
                PSHandSwapCards(res, i, arc4random_uniform(i+1));
            }
        }
    }
    return res;
}

NSArray* PSHandOrderedSuits(PSHandHash hand, PSGameSuitOrder order) {
    if (order == kSuitOrderByRank) {
        return @[
            @(PSCardSuitRising[0])
        ,   @(PSCardSuitRising[1])
        ,   @(PSCardSuitRising[2])
        ,   @(PSCardSuitRising[3])
        ];
    }
    PSCardSuit red[2], black[2];
    int redCount = 0, blackCount = 0;
    if (PSHandKeepSuit(hand, kSuitSpades)) {
        black[blackCount++] = kSuitSpades;
    }
    if (PSHandKeepSuit(hand, kSuitClubs)) {
        black[blackCount++] = kSuitClubs;
    }
    if (PSHandKeepSuit(hand, kSuitDiamonds)) {
        red[redCount++] = kSuitDiamonds;
    }
    if (PSHandKeepSuit(hand, kSuitHearts)) {
        red[redCount++] = kSuitHearts;
    }
    NSMutableArray *res = [NSMutableArray arrayWithCapacity:blackCount+redCount];
    int redPos = 0, blackPos = 0;
    if (blackCount >= redCount && blackPos != blackCount) {
        [res addObject:@(black[blackPos++])];
    }
    if (redPos != redCount) {
        [res addObject:@(red[redPos++])];
    }
    if (blackPos != blackCount) {
        [res addObject:@(black[blackPos++])];
    }
    assert(blackPos == blackCount);
    if (redPos != redCount) {
        [res addObject:@(red[redPos++])];
    }
    assert(redPos == redCount);
    return res;
}

	NSString *PSHandDescription(PSHandHash hand) {
    NSMutableString *res = [NSMutableString stringWithCapacity:128];
    for (int i = 0 ; i != PS_CARD_SUIT_RISING_LEN ; i++) {
        PSCardSuit suit = PSCardSuitRising[i];
        PSCardRankSet cardsInSuit = PSHandGetSuit(hand, suit);
        if (!cardsInSuit) {
            continue;
        }
        if (res.length) {
            [res appendString:@" "];
        }
        [res appendFormat:@"%@: ", PSCardSuitDescription(suit)];
        PSCardSetDescription(res, cardsInSuit);
    }
    return res;
}
